#!bash
#
# Syncany Bash Completion
# =======================
#
# Bash completion support for [Syncany](https://www.syncany.org/).
#
# The script is inspired by the BZR/Bazaar bash completion script
# available at https://launchpad.net/bzr-bash-completion
#
# Installation
# ------------
#
# 1. Place it in a `bash-completion.d` folder:
#
#   * /etc/bash-completion.d
#   * /usr/local/etc/bash-completion.d
#   * ~/bash-completion.d
#
# 2. Open new bash, and type `sy [TAB][TAB]`
#
# Documentation
# -------------
# The script is called by bash whenever [TAB] or [TAB][TAB] is pressed after
# 'sy (..)'. By reading entered command line parameters, it determines possible
# bash completions and writes them to the COMPREPLY variable. Bash then
# completes the user input if only one entry is listed in the variable or
# shows the options if more than one is listed in COMPREPLY.
#
# The script first determines the current parameter ($cur), the previous
# parameter ($prev), the first word ($firstword) and the last word ($lastword).
# Using the $firstword variable (= the command) and a giant switch/case,
# completions are written to $complete_words and $complete_options.
#
# If the current user input ($cur) starts with '-', only $command_options are
# displayed/completed, otherwise only $command_words.
#
# References
# ----------
# [1] http://stackoverflow.com/a/12495480/1440785
# [2] http://tiswww.case.edu/php/chet/bash/FAQ
#

shopt -s progcomp
_sy() {
	local cur prev firstword lastword complete_words complete_options

	# Don't break words at : and =, see [1] and [2]
	COMP_WORDBREAKS=${COMP_WORDBREAKS//[:=]}

	cur=${COMP_WORDS[COMP_CWORD]}
	prev=${COMP_WORDS[COMP_CWORD-1]}
	firstword=$(_sy_get_firstword)
	lastword=$(_sy_get_lastword)

	GLOBAL_COMMANDS="\
		cleanup\
		connect\
		daemon\
		down\
		genlink\
		init\
		log\
		ls\
		ls-remote\
		plugin\
		restore\
		status\
		up\
		watch"

	GLOBAL_OPTIONS="\
		-l --localdir\
		-d --debug\
		-h --help\
		-v -vv\
		--log\
		--loglevel\
		--print"

	GLOBAL_LOGLEVELS="\
		OFF\
		SEVERE\
		WARNING\
		INFO\
		FINE\
		FINER\
		FINEST\
		ALL"

	CONNECT_OPTIONS="\
		-P --plugin\
		-o --plugin-option\
		-n --add-daemon\
		   --password"

	DOWN_CONFLICT_STRATEGIES="\
		ask\
		rename"

	DOWN_OPTIONS="\
		-C --conflict-strategy\
		-A --no-apply"

	DAEMON_COMMANDS="\
		force-stop\
		reload\
		restart\
		start\
		status\
		stop\
		list\
		add\
		remove"

	GENLINK_OPTIONS="\
		-s --short\
		-m --machine-readable"

	INIT_OPTIONS="\
		-P --plugin\
		-o --plugin-option\
		-E --no-encryption\
		-G --no-compression\
		-t --create-target\
		-a --advanced\
		-n --add-daemon\
		-s --short\
		   --password"

	LOG_OPTIONS="\
		-n --database-count\
		-s --database-start\		
		-f --file-count\
		-x --exclude-empty"
		
	LS_OPTIONS="\
		-V --versions\
		-t --types\
		-D --date\
		-r --recursive\
		-q --deleted\
		-f --full-checksums\
		-g --group\
		-H --file-history"

	LS_TYPES="\
		t\
		d\
		f"

	PLUGIN_COMMANDS="\
		list\
		install\
		update\
		remove"

	PLUGIN_IDS_TRANSFER="\
		dropbox\
		ftp\
		local\
		raid0\
		s3\
		samba\
		sftp\
		swift\
		webdav"
		
	PLUGIN_IDS_ALL="\
		$PLUGIN_IDS_TRANSFER\
		flickr\
		gui"		
		
	PLUGIN_LIST_OPTIONS="\
		-R --remote-only\
		-L --local-only\
		-s --snapshots\
		-a --api-endpoint"

	PLUGIN_INSTALL_OPTIONS="\
		-s --snapshot\
		-m --minimal-output"

	PLUGIN_DROPBOX_OPTIONS="\
		authToken=\
		path="

	PLUGIN_FTP_OPTIONS="\
		hostname=\
		username=\
		password=\
		path=\
		port="

	PLUGIN_LOCAL_OPTIONS="\
		path="

	PLUGIN_S3_OPTIONS="\
		accessKey=\
		secretKey=\
		bucket=\
		location="

	PLUGIN_SAMBA_OPTIONS="\
		hostname=\
		username=\
		password=\
		share=\
		path="

	PLUGIN_SFTP_OPTIONS="\
		hostname=\
		username=\
		privatekey=\
		password=\
		path=\
		port="

	PLUGIN_SWIFT_OPTIONS="\
		authUrl=\
		username=\
		password="

	PLUGIN_WEBDAV_OPTIONS="\
		url=\
		username=\
		password="

	RESTORE_OPTIONS="\
		-r --revision\
		-t --target"

	STATUS_OPTIONS="\
		-f --force-checksum\
		-D --no-delete"

	UP_OPTIONS="\
		-R --no-resume\
		$STATUS_OPTIONS"

	WATCH_OPTIONS="\
		-i --interval\
		-s --delay\
		-W --no-watcher\
		-a --announce\
		-N --no-announcements\
		$UP_OPTIONS\
		$DOWN_OPTIONS"

	CLEANUP_OPTIONS="\
		-F --force\
		-o --delete-older-than\
		-I --no-delete-interval\
		-O --no-delete-older-than\
		-T --no-temp-removal\
		$STATUS_OPTIONS"

	# Un-comment this for debug purposes:
	#   echo -e "\nprev = $prev, cur = $cur, firstword = $firstword, lastword = $lastword\n"

	case "${firstword}" in
	cleanup)
		complete_options="$CLEANUP_OPTIONS"
		;;

	connect)
		case "${prev}" in
			--plugin|-P)
				complete_words="$PLUGIN_IDS_TRANSFER"
				;;

			--plugin-option|-o)
				complete_words=$(_sy_plugin_option_words) 

				if [[ ${#COMPREPLY[@]} != 1 || $complete_words = *= ]]; then
					compopt -o nospace
				fi
				;;

			*)
				complete_options="$CONNECT_OPTIONS"
				;;
		esac
		;;

	down)
		case "${prev}" in
			--conflict-strategy|-C)
				complete_words="$DOWN_CONFLICT_STRATEGIES"
				;;

			*)
				complete_options="$DOWN_OPTIONS"
				;;
		esac
		;;

	daemon)
		case "${lastword}" in
			force-stop|reload|restart|start|status|stop|list)
				;;
				
			add|remove)
				# Special handling: return directories, no space at the end

				compopt -o nospace
				COMPREPLY=( $( compgen -d -S "/" -- $cur ) )

				return 0
				;;

			*)
				complete_words="$DAEMON_COMMANDS"
				;;
		esac
		;;

	genlink)
		complete_options="$GENLINK_OPTIONS"
		;;

	init)
		case "${prev}" in
			--plugin|-P)
				complete_words="$PLUGIN_IDS_TRANSFER"
				;;

			--plugin-option|-o)
				complete_words=$(_sy_plugin_option_words) 

				if [[ ${#COMPREPLY[@]} != 1 || $complete_words = *= ]]; then
					compopt -o nospace
				fi

				;;

			*)
				complete_options="$INIT_OPTIONS"
				;;
		esac
		;;
		
	log)
		complete_options="$LOG_OPTIONS"
		;;

	ls)
		case "${prev}" in
			--types|-t)
				complete_words="$LS_TYPES"
				;;

			*)
				case "${cur}" in
					-*)
						complete_options="$LS_OPTIONS"
						;;

					*)
						# Special handling: return directories, no space at the end

						compopt -o nospace
						COMPREPLY=( $( compgen -d -S "/" -- $cur ) )

						return 0
						;;
				esac
				;;
		esac
		;;

	ls-remote)
		;;

	plugin)
		case "${lastword}" in
		list)
			complete_options="$PLUGIN_LIST_OPTIONS"
			;;
			
		install)
			complete_words="$PLUGIN_IDS_ALL"
			complete_options="$PLUGIN_INSTALL_OPTIONS"
			;;
			
		update)
			complete_words="$PLUGIN_IDS_ALL"
			;;
			
		remove)
			complete_words="$PLUGIN_IDS_ALL"
			;;

		*)
			complete_words="$PLUGIN_COMMANDS"
			;;
		esac
		;;

	restore)
		case "${prev}" in
			--target|-t)
				# Special handling: Return file names

				COMPREPLY=( $( compgen -f -- $cur ) )
				return 0
				;;

			*)
				complete_options="$RESTORE_OPTIONS"
				;;
		esac
		;;

	status)
		complete_options="$STATUS_OPTIONS"
		;;

	up)
		complete_options="$UP_OPTIONS"
		;;

	watch)
		case "${prev}" in
			--conflict-strategy|-C)
				complete_words="$DOWN_CONFLICT_STRATEGIES"
				;;

			*)
				complete_options="$WATCH_OPTIONS"
				;;
		esac
		;;

	*)
		case "${prev}" in
			--log|--localdir|-l)
				# Special handling: return directories, no space at the end

				compopt -o nospace
				COMPREPLY=( $( compgen -d -S "/" -- $cur ) )

				return 0
				;;

			--loglevel)
				complete_words="$GLOBAL_LOGLEVELS"
				;;

			*)
				complete_words="$GLOBAL_COMMANDS"
				complete_options="$GLOBAL_OPTIONS"
				;;
		esac
		;;
	esac

	# Either display words or options, depending on the user input
	if [[ $cur == -* ]]; then
		COMPREPLY=( $( compgen -W "$complete_options" -- $cur ))

	else
		COMPREPLY=( $( compgen -W "$complete_words" -- $cur ))
	fi

	return 0
}

## Helper functions ###

# Finds plugin identifier and determines possible plugin-specific settings
_sy_plugin_option_words() {
	local plugin_id i

	plugin_id=
	for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
		if [[ ${COMP_WORDS[i]} == "-P" ]] || [[ ${COMP_WORDS[i]} == "--plugin" ]]; then
			plugin_id=${COMP_WORDS[i+1]}
			break
		fi
	done

	# Output completions without prefix
	echo $(_sy_plugin_option_words_for_plugin "$plugin_id" "")
}

# Determines the plugin-specific settings set by --plugin-option or -o for a specific plugin
# Parameters: $1 is plugin ID (e.g. "ftp"), $2 is plugin prefix (e.g. "storage1.")
_sy_plugin_option_words_for_plugin() {
	local plugin_id="$1"
	local plugin_prefix="$2"
	local plugin_completions=""

	case "${plugin_id}" in
		dropbox)
			plugin_completions="$PLUGIN_DROPBOX_OPTIONS"
			;;

		ftp)
			plugin_completions="$PLUGIN_FTP_OPTIONS"
			;;

		local)
			plugin_completions="$PLUGIN_LOCAL_OPTIONS"
			;;

		raid0)
			plugin_completions=$(_sy_plugin_option_words_raid0)
			;;

		s3)
			plugin_completions="$PLUGIN_S3_OPTIONS"
			;;

		samba)
			plugin_completions="$PLUGIN_SAMBA_OPTIONS"
			;;

		sftp)
			plugin_completions="$PLUGIN_SFTP_OPTIONS"
			;;

		swift)
			plugin_completions="$PLUGIN_SWIFT_OPTIONS"
			;;

		webdav)
			plugin_completions="$PLUGIN_WEBDAV_OPTIONS"
			;;

		*)
			;;
	esac

	# Output with prefix
	for plugin_option in $plugin_completions; do
		echo "${plugin_prefix}${plugin_option}"
	done
}

# Determines the current plugin optons for RAID0 plugin, 
# including sub-options of other plugins
_sy_plugin_option_words_raid0() {
	local storage1_type storage2_type i

	storage1_type=
	storage2_type=
	for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
		if [[ $cur != ${COMP_WORDS[i]} && ${COMP_WORDS[i]} == storage1:type=* ]]; then
			storage1_type=${COMP_WORDS[i]/storage1:type=}
		fi

		if [[ $cur != ${COMP_WORDS[i]} && ${COMP_WORDS[i]} == storage2:type=* ]]; then
			storage2_type=${COMP_WORDS[i]/storage2:type=}
		fi
	done

	if [[ $storage1_type != "" && $storage1_type != "raid0" ]]; then
		echo $(_sy_plugin_option_words_for_plugin "$storage1_type" "storage1.")		
	else
		for plugin_id in $PLUGIN_IDS_TRANSFER; do
			echo "storage1:type=$plugin_id"
		done
	fi

	if [[ $storage2_type != "" && $storage2_type != "raid0" ]]; then
		echo $(_sy_plugin_option_words_for_plugin "$storage2_type" "storage2.")
	else
		for plugin_id in $PLUGIN_IDS_TRANSFER; do
			echo "storage2:type=$plugin_id"
		done
	fi
}

# Determines the first non-option word of the command line. This
# is usually the command
_sy_get_firstword() {
	local firstword i

	firstword=
	for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
		if [[ ${COMP_WORDS[i]} != -* ]]; then
			firstword=${COMP_WORDS[i]}
			break
		fi
	done

	echo $firstword
}

# Determines the last non-option word of the command line. This
# is usally a sub-command
_sy_get_lastword() {
	local lastword i

	lastword=
	for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
		if [[ ${COMP_WORDS[i]} != -* ]] && [[ -n ${COMP_WORDS[i]} ]] && [[ ${COMP_WORDS[i]} != $cur ]]; then
			lastword=${COMP_WORDS[i]}
		fi
	done

	echo $lastword
}

## Define bash completions ###

complete -F _sy sy
complete -F _sy syncany
